package session;

import entity.Category;
import java.util.Collection;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import java.io.*;
import java.net.*;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import org.w3c.dom.*;
import javax.net.ssl.HttpsURLConnection;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.dom.DOMSource;

/**
 * Gestisce le categorie.
 *
 * @author Rachid Ouchary, Fabio Strocco, Max Passaghe, Marco Tansini.
 */
@Stateless
public class CategoryManager implements CategoryManagerRemote {

    @EJB
    private CategoryFacadeRemote categoryFacade;
    private static String devID, appID, certID, serverUrl, userToken, compatLevel, siteID, verb, mapVerb;

    /*
     *  mantiene la versione delle categorie memorizzate,
     *  viene caricata tutte le volte che viene caricata la classe
     *  se == null allora non sono state mai scaricate le categorie
     */
    protected String version;
    protected Collection<Category> categorie;
    private String getCategXml, getMappingsXml;

    public CategoryManager() {

        devID = "08246f2e-09b3-44cc-9374-e6039922aed9";
        appID = "uni431a3c-f004-41de-89c9-4847a52d15f";
        certID = "fd4771f4-6a6a-41b1-9e24-43b41bc23593";
        serverUrl = "https://api.ebay.com/ws/api.dll";
        userToken = "AgAAAA**AQAAAA**aAAAAA**maynSw**nY+sHZ2PrBmdj6wVnY+sEZ2PrA2dj6wGmIqiCZaKqQSdj6x9nY+seQ**hdIAAA**AAMAAA**/ISYj8MDyPlJFXBsrtjzu+vyPpS6fyGehhN9LbDYEky996SSSaXSSVvJajhAi+TEE6sYLhlcpj/a775dpVVubxsvs3KYSmnGgb7HDVxhOhBA+noKWC8EZz7Bg2tMgdkEoY1l0iMqgtLRtINRtBJCpA+BImIRT9eu0eOTjPgsUXnOL827+pkmV9r/3SNEPAbkYyAa4lvQHNC3e9Ybn8HpJ+/mejMKTxexQiUJAJW6WfVh+kz+2iMpgiRWujWa0HLTFMoKDBviT/rScfbQJNEVe8c9nyreY6gXafv6pLhGwqHL1RgyaonYyM+ANg2YEEQxb2VNea+lBN+BOMfXbjEQcyPCnTxsiDkprYTy1+LBiSdNJWCu0Apr+B/tBJZXSBfVLxtb4tfMPlKI5XWYYQn+pAhi2CLc00DHvp0oNdl3qgdEt5Q3p4gb/7Uvf1FbEPBDvbIzVtvvGRa+tPe2Lvw/OisAGYTlxBj/tlZjpwQrulcgcvcG47splIteC8+UguaLnUXs10kdW+03tQQYEYX0a6XcCL9snBPOmRgwnzisiZQ2Caega1Za4uczT5fXHyPIZlDjWHSPH8exIiW3FTAG4TR4nzj2Hst9iQEY+P+JtuN187B0G011jNf/x9DkAjw/wa6Lv0/6Orw7IymPbH+fHpHVgpjFZqFgKFoXeg+bgTw/LYxNOeVaqXQ1f2Pbjj+wE3oW4HPuLNXsq/d7DGM0VWvrUoNbNM/GwU97EMv0ERwStqb4XrbrT3RSwMEXOK4d";
        compatLevel = "577";
        siteID = "101"; //(italia)
        //SET THE NAME OF THE CALL BEING MADE!
        verb = "GetCategories";
        mapVerb = "GetCategoryMappings";
        getCategXml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><GetCategoriesRequest xmlns=\"urn:ebay:apis:eBLBaseComponents\"><RequesterCredentials><eBayAuthToken>value</eBayAuthToken></RequesterCredentials> <DetailLevel>ReturnAll</DetailLevel><LevelLimit>2</LevelLimit> <ViewAllNodes>true</ViewAllNodes></GetCategoriesRequest>";
        getMappingsXml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><GetCategoryMappingsRequest xmlns=\"urn:ebay:apis:eBLBaseComponents\"><RequesterCredentials><eBayAuthToken>" + userToken + "</eBayAuthToken></RequesterCredentials><WarningLevel>High</WarningLevel><DetailLevel>ReturnAll</DetailLevel><CategoryVersion>" + version + "</CategoryVersion></GetCategoryMappingsRequest>";

        categorie = null; //TODO: scegliere se inizializzare o meno
        version = null;

    }

    /**
     * Get the value of version
     *
     * @return the value of version
     */
    public String getVersion() {
        return version;
    }

    /**
     * Set the value of version
     *
     * @param version new value of version
     */
    protected void setVersion(String version) {
        this.version = version;
    }

    /**
     * Get the value of categorie
     *
     * @return the value of categorie
     */
    public Collection<Category> getCategorie() {
        return categorie;
    }

    /**
     * Set the value of categorie
     *
     * @param categorie new value of categorie
     */
    protected void setCategorie(Collection<Category> categorie) {
        this.categorie = categorie;
    }

    /**
     * Returns true iff the version of the categories
     * it's equal to the online version
     *
     * @param version the version to compare
     */
    public boolean updated() {
        //scarica la versione online
        String onlineVersion = getOnlineVersionNumber();
        //aggiorna la variabile version
        this.version = categoryFacade.findCategoryVersion();
        if (this.version != null) {
            return this.version.equals(onlineVersion);
        } else {
            return false;
        }
    }

    /**
     * Downloads the categories from ebay's api and
     * make it persistent
     */
    public void populateCategories() {
        //aggiorna la variabile version
        this.version = categoryFacade.findCategoryVersion();
        if (version != null) {
            System.out.println("populate già fatto version="+version);//TODO remove
            return;
        }

        Document doc;
        try {
            //Create a HttpsURLConnection with the correct server
            URL server = new URL(serverUrl);
            HttpsURLConnection connection = (HttpsURLConnection) (server.openConnection());

            //set the connection to do both input and output. Request Method = POST
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setRequestMethod("POST");

            //Add the necessary headers to the connection
            addeBayHTTPHeaders(connection, devID, appID, certID, compatLevel, verb, siteID);
            //Send the request for just the Version number by setting ViewAllNodes = false and DetailLevel=""
            doc = SendRequest(connection, userToken, "ReturnAll", siteID, verb, true, getCategXml);
            //return the version number

        } catch (Exception e) {
            System.out.println("ERROR - populatecategories: " + e.toString());
            e.printStackTrace();
            return;
        }



        NodeList categoryArray = doc.getElementsByTagName("CategoryArray");
        //System.out.println("catarraylen: " + categoryArray.getLength());
        NodeList nodi = categoryArray.item(0).getChildNodes();
        //System.out.println("nodelist: " + nodi.getLength());
        Node nodo = null;
        CharacterData dato;
        NodeList figli;
        String categoryName = "", categoryLvl = "", categoryID = "", categoryParent = "";
        LinkedList<Category> catLvl2 = new LinkedList<Category>();
        HashMap<String, Category> catLvl1 = new HashMap<String, Category>();

        for (int i = 0; i < nodi.getLength(); i++) {
            nodo = nodi.item(i);
            figli = nodo.getChildNodes();

            for (int k = 0; k < figli.getLength(); k++) {
                Node figlio = figli.item(k);
                String nodeName = figlio.getNodeName();
                dato = (CharacterData) figlio.getFirstChild();

                if (nodeName.equals("CategoryName")) {
                    categoryName = dato.getData();
                    //System.out.println("CategoryName= " + categoryName);
                } else if (nodeName.equals("CategoryID")) {
                    categoryID = dato.getData();
                    //System.out.println("CategoryID= " + categoryID);
                } else if (nodeName.equals("CategoryLevel")) {
                    categoryLvl = dato.getData();
                    //System.out.println("CategoryLevel= " + categoryLvl);
                } else if (nodeName.equals("CategoryParentID")) {
                    categoryParent = dato.getData();
                    //System.out.println("CategoryParentID= " + categoryParent);
                }

            }
            Category c = new Category(categoryName, categoryID, categoryParent, categoryLvl);
            if (categoryLvl.equals("1")) {
                //System.out.println("catlvl1: "+c);
                catLvl1.put(categoryID, c);
            } else if (categoryLvl.equals("2")) {
                //System.out.println("catlvl2: "+c);
                catLvl2.add(c);
            } else {
                throw new RuntimeException("Categoria con livello sconosciuto trovata in populateCat(): lvl=" + categoryLvl);
            }

        }

        for (Category cat : catLvl2) {
            Category parent = catLvl1.get(cat.getParentID());
            if (parent == null) {
                throw new RuntimeException("Categoria padre non trovata populateCat(): parent=" + cat.getParentID());
            }
            parent.getSottoCategorie().add(cat);
        }


        Collection<Category> padri = catLvl1.values();
        for (Category cat : padri) {
            categoryFacade.create(cat);
        }

        // prendo la versione delle categorie che ho scaricato
        String catVersion = doc.getElementsByTagName("CategoryVersion").item(0).getChildNodes().item(0).getNodeValue();
        Category versionCategory = new Category(catVersion, entity.Category.VERSION_CATEGORY_ID, null, null);
        categoryFacade.create(versionCategory);
        version = catVersion;

    }

    /**
     * Sends the request to the Server and returns the Document (XML)
     * that is returned as the response.
     * @param connection The HttpsURLConnection object to be used to execute the request
     * @param requestToken The Token of the eBay user who is making the call.
     * @param detailLevel Controls the amount or level of data returned ("0" is default)
     * @param siteID Indicates the eBay site associated with the call (0 = US, 2 = Canada, 3 = UK, ...)
     * @param verb The function being called (e.g. "GeteBayOfficialTime")
     * @param viewAllNodes If true all nodes are returned, if false only leaf nodes returned
     * @return The Document (Xml) returned from the server after the request was made.
     */
    private static Document SendRequest(HttpsURLConnection connection, String requestToken,
            String detailLevel, String siteID, String verb,
            boolean viewAllNodes, String requestXml) {

        try {
            //Get the XML string into a Document object
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuild = factory.newDocumentBuilder();
            Document xmlDoc = docBuild.parse(stringToInputStream(requestXml));

            //Set the values of the nodes
            xmlDoc.getElementsByTagName("eBayAuthToken").item(0).getChildNodes().item(0).setNodeValue(requestToken);

            if (!detailLevel.equals("")) {
                xmlDoc.getElementsByTagName("DetailLevel").item(0).getChildNodes().item(0).setNodeValue(detailLevel);
            } else //remove if empty string
            {
                xmlDoc.getElementsByTagName("GetCategoriesRequest").item(0).removeChild(xmlDoc.getElementsByTagName("DetailLevel").item(0));
            }

            //xmlDoc.getElementsByTagName("CategorySiteID").item(0).getChildNodes().item(0).setNodeValue(siteID);

            if (viewAllNodes) {
                xmlDoc.getElementsByTagName("ViewAllNodes").item(0).getChildNodes().item(0).setNodeValue("true");
            } else {
                xmlDoc.getElementsByTagName("ViewAllNodes").item(0).getChildNodes().item(0).setNodeValue("false");
            }


            //Get the output stream of the connection
            OutputStream out = connection.getOutputStream();

            //Transform and write the Document to the stream
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer tr = tf.newTransformer();
            Source input = new DOMSource(xmlDoc);
            Result output = new StreamResult(out);
            tr.transform(input, output);
            out.flush();
            out.close();

            //Get the Input stream for the response
            InputStream in = connection.getInputStream();
            //Get the stream into a Document object
            Document response = docBuild.parse(in);
            //close the input stream and connection
            in.close();
            connection.disconnect();

            //return the response XML Document
            return response;



        } catch (IOException e) {
            System.out.println("SendRequest IO Error: " + e.toString());
            e.printStackTrace();
            return null;
        } catch (Exception e) {
            System.out.println("Error Sending Request: " + e.toString());
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Sends the request to the Server and returns the Document (XML)
     * that is returned as the response.
     * @param connection The HttpsURLConnection object to be used to execute the request
     * @param requestToken The Token of the eBay user who is making the call.
     * @param detailLevel Controls the amount or level of data returned ("0" is default)
     * @param siteID Indicates the eBay site associated with the call (0 = US, 2 = Canada, 3 = UK, ...)
     * @param verb The function being called (e.g. "GeteBayOfficialTime")
     * @param viewAllNodes If true all nodes are returned, if false only leaf nodes returned
     * @return The Document (Xml) returned from the server after the request was made.
     */
    private static Document SendMapRequest(HttpsURLConnection connection, String requestToken,
            String categoryVersion, String requestXml) {

        try {
            //Get the XML string into a Document object
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuild = factory.newDocumentBuilder();
            Document xmlDoc = docBuild.parse(stringToInputStream(requestXml));

            //Set the values of the nodes
            xmlDoc.getElementsByTagName("eBayAuthToken").item(0).getChildNodes().item(0).setNodeValue(requestToken);


            xmlDoc.getElementsByTagName("DetailLevel").item(0).getChildNodes().item(0).setNodeValue("ReturnAll");


            xmlDoc.getElementsByTagName("CategoryVersion").item(0).getChildNodes().item(0).setNodeValue(categoryVersion);




            //Get the output stream of the connection
            OutputStream out = connection.getOutputStream();

            //Transform and write the Document to the stream
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer tr = tf.newTransformer();
            Source input = new DOMSource(xmlDoc);
            Result output = new StreamResult(out);
            tr.transform(input, output);
            out.flush();
            out.close();

            //Get the Input stream for the response
            InputStream in = connection.getInputStream();
            //Get the stream into a Document object
            Document response = docBuild.parse(in);
            //close the input stream and connection
            in.close();
            connection.disconnect();

            //return the response XML Document
            return response;



        } catch (IOException e) {
            System.out.println("SendRequest IO Error: " + e.toString());
            e.printStackTrace();
            return null;
        } catch (Exception e) {
            System.out.println("Error Sending Request: " + e.toString());
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Return the version number of the Category tree currently available
     * from the eBay API
     * @return version number of the category tree currently available online
     */
    public String getOnlineVersionNumber() {
        try {
            //Create a HttpsURLConnection with the correct server
            URL server = new URL(serverUrl);
            HttpsURLConnection connection = (HttpsURLConnection) (server.openConnection());

            //set the connection to do both input and output. Request Method = POST
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setRequestMethod("POST");

            //Add the necessary headers to the connection
            addeBayHTTPHeaders(connection, devID, appID, certID, compatLevel, verb, siteID);
            //Send the request for just the Version number by setting ViewAllNodes = false and DetailLevel=""
            Document xmlDoc = SendRequest(connection, userToken, "ReturnAll", siteID, verb, false, getCategXml);
            //return the version number
            return xmlDoc.getElementsByTagName("CategoryVersion").item(0).getChildNodes().item(0).getNodeValue();
        } catch (Exception e) {
            System.out.println("ERROR - GetOnlineVersionNumber: " + e.toString());
            e.printStackTrace();
            return null;
        }

    }

    /**
     * Adds the necessary headers to the HttpsURLConnection passed in
     * order for the call to be successful
     * @param connection The HttpsURLConnection to add the headers to
     * @param devID Developer ID, as registered with the Developer's Program.
     * @param appID Application ID, as registered with the Developer's Program.
     * @param certID Certificate ID, as registered with the Developer's Program.
     * @param compatLevel Regulates versioning of the XML interface for the API.
     * @param verb Name of the function being called e.g. "GetItem"
     * @param siteID The Id of the eBay site the call should be executed on
     */
    private static void addeBayHTTPHeaders(HttpsURLConnection connection,
            String devID, String appID,
            String certID, String compatLevel,
            String verb, String siteID) {
        // Add the Compatibility Level Header
        connection.addRequestProperty("X-EBAY-API-COMPATIBILITY-LEVEL", compatLevel);

        // Add the Developer Name, Application Name, and Certification Name Headers
        connection.addRequestProperty("X-EBAY-API-DEV-NAME", devID);
        connection.addRequestProperty("X-EBAY-API-APP-NAME", appID);
        connection.addRequestProperty("X-EBAY-API-CERT-NAME", certID);

        // Add the API verb Header
        connection.addRequestProperty("X-EBAY-API-CALL-NAME", verb);

        // Add the Site Id Header
        connection.addRequestProperty("X-EBAY-API-SITEID", siteID);

        // Add the Content-Type Header
        connection.addRequestProperty("Content-Type", "text/xml");
    }

    /*
     * Convert String to InputString using ByteArrayInputStream class.
     * This class constructor takes the string byte array which can be
     * done by calling the getBytes() method.
     */
    private static InputStream stringToInputStream(String text) {
        InputStream is = null;
        try {
            is = new ByteArrayInputStream(text.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return is;

    }

    public void updateCategories() {
        if (updated()) {
            return;
        }

        Document doc;
        try {
            //Create a HttpsURLConnection with the correct server
            URL server = new URL(serverUrl);
            HttpsURLConnection connection = (HttpsURLConnection) (server.openConnection());

            //set the connection to do both input and output. Request Method = POST
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setRequestMethod("POST");

            //Add the necessary headers to the connection
            addeBayHTTPHeaders(connection, devID, appID, certID, compatLevel, mapVerb, siteID);
            //retrieve the categorymappings xml
            doc = SendMapRequest(connection, userToken, this.version, getMappingsXml);


        } catch (Exception e) {
            System.out.println("ERROR - updateCategories: " + e.toString());
            e.printStackTrace();
            return;
        }
        Node root = doc.getDocumentElement();
        NodeList mappings = root.getChildNodes();

        Node n = null;
        NamedNodeMap nodeMap = null;
        String nodeName = "", newVersion = "";
        List<Category> categorie = categoryFacade.findAll();

        HashMap<String, Category> categoryMap = new HashMap<String, Category>();
        for (Category c : categorie) {
            categoryMap.put(c.getEbayID(), c);
        }

        for (int i = 0; i < mappings.getLength(); i++) {
            n = mappings.item(i);
            nodeName = n.getNodeName();
            //System.out.println(nodeName);
            if (nodeName.equals("CategoryMapping")) {
                nodeMap = n.getAttributes();
                String newID = nodeMap.getNamedItem("id").getNodeName();
                String oldID = nodeMap.getNamedItem("oldID").getNodeName();
                //System.out.println("id:" + newID + "oldID:" + oldID);
                if (categoryMap.containsKey(oldID)) {
                    Category temp = categoryMap.get(oldID);
                    temp.setEbayID(newID);
                    categoryFacade.edit(temp);
                } else {
                    System.out.println("OldID (" + oldID + ") not found in DB");
                }
            } else if (nodeName.equals("CategoryVersion")) {
                newVersion = n.getChildNodes().item(0).getNodeValue();
                //System.out.println("New Version = " + newVersion);
                Category temp = categoryMap.get(Category.VERSION_CATEGORY_ID);
                temp.setEbayID(newVersion);
                categoryFacade.edit(temp);
            } else {
                System.out.println("nodo ignorato");
            }


        }

    }
}